package com.aaron.extension;


import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * 参考的是dubbo spi机制。
 */
@Slf4j
public final class ExtensionLoader<T> {
    // 配置文件path
    private static final String SERVICE_DIRECTORY = "META-INF/extensions/";
    // 保存的是ExtensionLoader实例，一个拓展类就有一个ExtensionLoader实例。扩展加载器ExtensionLoader
    private static final Map<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
    // 保存的是扩展类型到对象的映射。key:接口类型，value:实例化对象。扩展对象
    private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
    // 某个接口类。xxxxx.class
    private final Class<?> type;
    // 保存的是 扩展名 到实例对象的映射。holder持有了已经实例化的拓展对象
    private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    // 保存的是所有可扩展接口类型。本holder里面保存的是map集合，map:key-clazzName,value-clazz，是类型的缓存，创建扩展实例需要使用到它的缓存
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    // 构造方法
    private ExtensionLoader(Class<?> type) {
        this.type = type;
    }

    /**
     * 获取ExtensionLoader整个
     * 首先从缓存中获取与拓展类对应的 ExtensionLoader，若缓存未命中，则创建一个新的实例。
     * 这个类必须是使用@SPI注解的接口类
     */
    public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {
        // 查看这个类要获取实例时（也就是拓展类），符不符合规则。
        if (type == null) {
            throw new IllegalArgumentException("Extension type should not be null.");
        }
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type must be an interface.");
        }
        if (type.getAnnotation(SPI.class) == null) {
            throw new IllegalArgumentException("Extension type must be annotated by @SPI");
        }
        // 首先在cache中查找，与之对应的实例化对象
        ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
        // 如果cache没有命中，那么就创建一个新的实例。创建完成之后，再在EXTENSION_LOADERS中获取。
        // putIfAbsent()方法：如果传入key对应的value已经存在，就返回存在的value，不进行替换。如果不存在，就添加key和value，返回null
        if (extensionLoader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));
            extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
        }
        return extensionLoader;
    }

    /**
     * 通过ExtensionLoader实例 的getExtension方法获取  拓展类  对象.
     * 首先检查缓存，缓存未命中则创建拓展对象。
     * 这里入参的name假设等于gzip
     */
    public T getExtension(String name) {
        // 传入的参数就是：全限定的接口名
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("Extension name should not be null or empty.");
        }
        // Holder仅用于持有目标对象，没其他什么逻辑
        Holder<Object> holder = cachedInstances.get(name);

        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        // 没有拓展实例不存在的话，就创建一个实例。以下是双重判断
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 创建拓展实例，createExtension(name)方法返回的就是 实例对象
                    instance = createExtension(name);
                    // 此时该holder对象里就有该实例对象了
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

    // 创建拓展对象实例，name="gzip"
    private T createExtension(String name) {
        // 从配置文件中加载所有的拓展类，形成配置项名称到配置类的映射关系。得到的是类型
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw new RuntimeException("No such extension of name " + name);
        }
        // 在缓存中判断是否有这个拓展类对象实例
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            try {
                // 创建实例，这里才是实实在在的创建了实例对象
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                // 创建完再获取
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
        return instance;
    }

    /**
     * 我们在通过名称获取拓展类之前，
     * 首先需要根据配置文件解析出名称到拓展类的映射，也就是 Map<名称, 拓展类>。
     * 之后再从 Map 中取出相应的拓展类即可
     * key=gzip,value=
     */
    // 这里也是先检查缓存，若缓存未命中，则通过 synchronized 加锁。
    // 加锁后再次检查缓存，并判空。此时如果 classes 仍为 null，则加载拓展类。以上代码的写法是典型的双重检查锁.
    private Map<String, Class<?>> getExtensionClasses() {
        // 从缓存中获取  拓展类（class）
        Map<String, Class<?>> classes = cachedClasses.get();
        // double check
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = new HashMap<>();
                    // load all extensions from our extensions directory
                    // 从指定文件夹配置文件中 加载所有的拓展类
                    loadDirectory(classes);
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

    // 从指定文件夹配置文件中 加载所有的拓展类.extensionClasses这个Map集合中保存的就是<gzip, GzipCompress.class>
    private void loadDirectory(Map<String, Class<?>> extensionClasses) {
        // fileName = 文件夹路径 + type 全限定名。所以文件名必须以type的全限定名命名
        String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
        try {
            Enumeration<URL> urls;
            ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
            // 根据文件名加载所有的同名文件
            urls = classLoader.getResources(fileName);
            //
            if (urls != null) {
                // 这里的while循环，得到URL
                while (urls.hasMoreElements()) {
                    URL resourceUrl = urls.nextElement();
                    // 加载资源
                    loadResource(extensionClasses, classLoader, resourceUrl);
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    // 加载资源，读取所有的配置文件信息，并全部加载到extensionClasses中
    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {

            String line;
            // read every line，按行读取配置内容
            while ((line = reader.readLine()) != null) {
                // get index of comment
                final int ci = line.indexOf('#');
                if (ci >= 0) {
                    // 截取#之前的字符串，#之后的内容为注释,忽略
                    line = line.substring(0, ci);
                }
                // 去除首位空格
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        final int ei = line.indexOf('=');
                        // 以 = 为界，截取键与值。比如 gzip=com.aaron.compress.gzip.GzipCompress
                        String name = line.substring(0, ei).trim();
                        String clazzName = line.substring(ei + 1).trim();
                        // 我们的SPI使用键值对，因此它们两个都不能是空的
                        if (name.length() > 0 && clazzName.length() > 0) {
                            // 类加载器 根据名字 加载某个类
                            Class<?> clazz = classLoader.loadClass(clazzName);
                            extensionClasses.put(name, clazz); // 加入map对象中
                        }
                    } catch (ClassNotFoundException e) {
                        log.error(e.getMessage());
                    }
                }
            }

        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }
}
